#include "tcpsocket.h"

#include <errno.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include "ibuffer.h"
#include "ireactor.h"
#include "isockethandler.h"

#include "env.h"
#include "netaddress.h"
#include "internalenv.h"

namespace net {

char* tcpsocket_buffer = new char[MAX_TCPSOCKET_BUFFER];

}

namespace net {

TCPSocket::TCPSocket() {
	fd = socket(AF_INET, SOCK_STREAM, 0);
	assert(fd != -1);

	Init();
}

TCPSocket::TCPSocket(uint32_t fd) {
	this->fd = fd;

	Init();
}

TCPSocket::~TCPSocket() {
	if(fd != -1) {
		close(fd);
		fd = -1;
	}

	if(in_buffer) {
		delete in_buffer;
		in_buffer = NULL;
	}

	if(out_buffer) {
		delete out_buffer;
		out_buffer = NULL;
	}

    if (reactor) {
        reactor->RemoveEvent(this);
    }
}

void TCPSocket::Init() {
	state = Invalild;

	handler = NULL;

	in_buffer = env::GetBuffer();
	out_buffer = env::GetBuffer();

	reactor = GetReactor();
	reactor->AddEvent(this, Read | Write);

	SetBlock(true);
}

SocketInfo& TCPSocket::GetSocketInfo() {
}

void TCPSocket::SetHandler(ISocketHandler* handler) {
	this->handler = handler;
}

IBuffer* TCPSocket::GetInBuffer() {
	return in_buffer;
}

IBuffer* TCPSocket::GetOutBuffer() {
	return out_buffer;
}

bool TCPSocket::SetBlock(bool is_block) {
	int flag = fcntl(fd, F_GETFL);
	if(flag == -1) {
		return false;
	}

	if(is_block) {
		flag = fcntl(fd, F_SETFL, flag | O_NONBLOCK);
	}
	else {
		flag = fcntl(fd, F_SETFL, flag & (~O_NONBLOCK));
	}

	if(flag == -1) {
		return false;
	}
	return true;
}

uint32_t TCPSocket::Connect(NetAddress& address) {
	sockaddr_in addr = {0};
	addr.sin_family = AF_INET;
	addr.sin_port = address.GetNetPort();
	addr.sin_addr.s_addr = inet_addr(address.GetSIp().data());

	socklen_t len = sizeof(sockaddr_in);
	int ret = connect(fd, (sockaddr*)&addr, len);
    return ret != -1;
}

uint32_t TCPSocket::Send(const char* data, uint32_t len) {
	out_buffer->Write((char*)data, len);	
	Send();
	return true;
}

void TCPSocket::Close() {
	Destroy();
}

void TCPSocket::Destroy() {
	delete this;
}

uint32_t TCPSocket::GetFd() {
	return fd;
}

void TCPSocket::OnEvent(uint32_t event_type) {
	if(handler == NULL) {
		Destroy();
		return ;
	}

    if (state == Invalild) {
        OnConnect();
    }

    if(state == Connected) {
        if (event_type & EPOLLIN) {
            OnRead();
        }
        else if (event_type & EPOLLOUT) {
		    OnWrite();
        }
        else if (event_type & EPOLLRDHUP) {
            state = Closing;
        }
    }

    if (state == Erroring) {
        OnError();
    }
    if(state == Closing) {
        OnClose();
    }
}

void TCPSocket::OnRead() {
	while(1) {
		int ret = recv(fd, tcpsocket_buffer, MAX_TCPSOCKET_BUFFER, 0);
		if(ret > 0) {
			in_buffer->Write(tcpsocket_buffer, ret);
		}
		else if(ret == 0) { /* Close */
            state = Closing;
            return ;
		}
		else {
			if(errno == EAGAIN || errno == EWOULDBLOCK) { 
				break; 
			}
            state = Erroring;
            return ;
		}
	}
    handler->OnRead(this);
}

void TCPSocket::OnWrite() {
    Send();
}

void TCPSocket::OnError() {
    handler->OnError(this, errno);
    state = Closing;
}

void TCPSocket::OnClose() {
    state = Closing;
    handler->OnClose(this);
    Close();
}

void TCPSocket::Send() {
	while(1) {
		out_buffer->Hold();
		int remain = out_buffer->Read(tcpsocket_buffer, MAX_TCPSOCKET_BUFFER); /* 1.读取数据 */
		if(remain == 0) {
			break;
		}

		int ret = send(fd, tcpsocket_buffer, remain, 0); /* 2.把它发送出去 */
		if(ret > 0) {
			out_buffer->Commit(ret); /* 3.把成功发送的内存提交了 */
		}
		if(ret == -1) {
			if(errno == EWOULDBLOCK) {
				break;
			}

			if(errno == EAGAIN) {
				continue;
			}
			else {
                state = Erroring;
                return ;
			}
		}
	}
    handler->OnWrite(this);
}

void TCPSocket::OnConnect() {
	socklen_t len = sizeof(int);

	int error = 0;
	if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == 0) {
		if(error == 0) {
            state = Connected;
			handler->OnConnected(this);
		}
		else {
            OnError();
		}
	}
}

}
